package fons.model.ontology;

import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;

import com.google.common.collect.Sets;

public class DimensionCollection {
	private final Context context;
	
	public DimensionCollection (final Context context) {
		this.context = context;
	}
	
	public DimensionCollection union() {
		checkModifiability();
		connections.push(Connection.UNION);
		return this;
	}
	
	public DimensionCollection without() {
		checkModifiability();
		connections.push(Connection.WITHOUT);
		return this;
	}
	
	public DimensionCollection intersection() {
		checkModifiability();
		connections.push(Connection.INTERSECTION);
		return this;
	}
	
	public DimensionCollection anyChildOf() {
		checkModifiability();
		operations.push(Operation.ANY_CHILD_OF);
		return this;
	}
	
	public DimensionCollection anyPartOf() {
		checkModifiability();
		operations.push(Operation.ANY_PART_OF);
		return this;
	}
	
	public DimensionCollection allLeavesOf() {
		checkModifiability();
		operations.push(Operation.ALL_LEAVES_OF);
		return this;
	}
	
	public DimensionCollection allLeavesAndIntermediariesOf() {
		checkModifiability();
		operations.push(Operation.ALL_LEAVES_AND_INTERMEDIARIES_OF);
		return this;
	}
	
	public String[] toStringArray() {
		final Set<Dimension> dimensions = get();

		List<String> result = new Vector<String>();
		for (Dimension d: dimensions) {
			result.add(d.getName());
		}

		return result.toArray(new String[0]);
	}
	
	public DimensionCollection dimension(Dimension dimension) {
		checkModifiability();
		
		Set<Dimension> result = new HashSet<Dimension>();
		result.add(dimension);
		
		while (!operations.isEmpty()) {
			final Operation operation = operations.pop();

			switch (operation) {
			case ANY_CHILD_OF:
				{
					Set<Dimension> tmp = new HashSet<Dimension>();
					for (Dimension d: result) {
						tmp.addAll(d.children());
					}
					result = tmp;
					break;
				}
			case ANY_PART_OF:
				{
					Set<Dimension> tmp = new HashSet<Dimension>();
					for (Dimension d: result) {
						tmp.addAll(d.parts());
					}
					result = tmp;
					break;
				}
			case ALL_LEAVES_OF:
				{
					Set<Dimension> tmp = Sets.newHashSet();
					for (Dimension d: result) {
						tmp.addAll(d.getDeepChildren());
					}
					result = tmp;
					break;
				}
			case ALL_LEAVES_AND_INTERMEDIARIES_OF:
				{
					Set<Dimension> tmp = Sets.newHashSet();
					for (Dimension d: result) {
						tmp.addAll(d.getFamily());
					}
					result = tmp;
					break;
				}
			}
		}
		
		dimensions.push(result);
		return this;
	}
	
	private Set<Dimension> precomputed;
	
	public Set<Dimension> get() {
		if (precomputed != null) return precomputed;
			
		if (dimensions.size() < 1) {
			precomputed = new HashSet<Dimension>();
			
		} else {
			
			Set<Dimension> set = new HashSet<Dimension>(dimensions.removeLast());

			while (!dimensions.isEmpty()) {
				if (connections.isEmpty()) {
					throw new IllegalStateException("Illegal Dimension-Collection definition! No connection specified!");
				}

				final Connection connection = connections.removeLast();
				final Set<Dimension> other = dimensions.removeLast();

				switch (connection) {
				case UNION: set = Sets.union(set, other); break;
				case WITHOUT: set = Sets.difference(set, other); break;
				case INTERSECTION: set = Sets.intersection(set, other); break;
				}
			}

			precomputed = Collections.unmodifiableSet(set);
		}
		
		return precomputed;
	}
	
	private void checkModifiability() {
		if (precomputed != null) {
			throw new IllegalStateException("This Dimension-Collection cannot be modified");
		}
	}
	
	private enum Operation { 
		ANY_CHILD_OF, ANY_PART_OF, ALL_LEAVES_OF, ALL_LEAVES_AND_INTERMEDIARIES_OF
	};
	
	private enum Connection {
		UNION, WITHOUT, INTERSECTION
	}
	
	private Stack<Operation> operations = new Stack<Operation>();
	private Deque<Connection> connections = new ArrayDeque<Connection>();
	private Deque<Set<Dimension>> dimensions = new ArrayDeque<Set<Dimension>>();

	public DimensionCollection dimension(String dimensionName) {
		return this.dimension(context.getDimension(dimensionName));
	}
	
	@Override
	public String toString() {
		return get().toString();
	}
}
